@juangadm/pre-post 1.2.0 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/README.md +52 -182
  2. package/dist/baseline.d.ts +122 -3
  3. package/dist/baseline.d.ts.map +1 -1
  4. package/dist/baseline.js +491 -46
  5. package/dist/baseline.js.map +1 -1
  6. package/dist/bin/cli.js +41 -17
  7. package/dist/bin/cli.js.map +1 -1
  8. package/dist/browser.d.ts +38 -3
  9. package/dist/browser.d.ts.map +1 -1
  10. package/dist/browser.js +132 -11
  11. package/dist/browser.js.map +1 -1
  12. package/dist/commands/compare.d.ts.map +1 -1
  13. package/dist/commands/compare.js +5 -5
  14. package/dist/commands/compare.js.map +1 -1
  15. package/dist/commands/detect.d.ts +5 -0
  16. package/dist/commands/detect.d.ts.map +1 -1
  17. package/dist/commands/detect.js +3 -0
  18. package/dist/commands/detect.js.map +1 -1
  19. package/dist/commands/doctor.d.ts.map +1 -1
  20. package/dist/commands/doctor.js +9 -3
  21. package/dist/commands/doctor.js.map +1 -1
  22. package/dist/commands/pr.d.ts +13 -1
  23. package/dist/commands/pr.d.ts.map +1 -1
  24. package/dist/commands/pr.js +222 -107
  25. package/dist/commands/pr.js.map +1 -1
  26. package/dist/comparison.d.ts +14 -0
  27. package/dist/comparison.d.ts.map +1 -1
  28. package/dist/comparison.js +48 -25
  29. package/dist/comparison.js.map +1 -1
  30. package/dist/diff-worker.d.ts +1 -0
  31. package/dist/diff-worker.d.ts.map +1 -1
  32. package/dist/diff-worker.js +1 -0
  33. package/dist/diff-worker.js.map +1 -1
  34. package/dist/diff.d.ts +42 -0
  35. package/dist/diff.d.ts.map +1 -1
  36. package/dist/diff.js +142 -24
  37. package/dist/diff.js.map +1 -1
  38. package/dist/doctor.d.ts +4 -1
  39. package/dist/doctor.d.ts.map +1 -1
  40. package/dist/doctor.js +5 -1
  41. package/dist/doctor.js.map +1 -1
  42. package/dist/drift.d.ts +29 -0
  43. package/dist/drift.d.ts.map +1 -0
  44. package/dist/drift.js +106 -0
  45. package/dist/drift.js.map +1 -0
  46. package/dist/errors.d.ts +13 -0
  47. package/dist/errors.d.ts.map +1 -1
  48. package/dist/errors.js +13 -0
  49. package/dist/errors.js.map +1 -1
  50. package/dist/github.d.ts +34 -3
  51. package/dist/github.d.ts.map +1 -1
  52. package/dist/github.js +102 -11
  53. package/dist/github.js.map +1 -1
  54. package/dist/report.d.ts +27 -1
  55. package/dist/report.d.ts.map +1 -1
  56. package/dist/report.js +144 -38
  57. package/dist/report.js.map +1 -1
  58. package/dist/routes/imports.d.ts +21 -1
  59. package/dist/routes/imports.d.ts.map +1 -1
  60. package/dist/routes/imports.js +32 -2
  61. package/dist/routes/imports.js.map +1 -1
  62. package/dist/routes.d.ts +18 -0
  63. package/dist/routes.d.ts.map +1 -1
  64. package/dist/routes.js +110 -29
  65. package/dist/routes.js.map +1 -1
  66. package/dist/run.d.ts +46 -12
  67. package/dist/run.d.ts.map +1 -1
  68. package/dist/run.js +138 -17
  69. package/dist/run.js.map +1 -1
  70. package/dist/sheet.d.ts +24 -0
  71. package/dist/sheet.d.ts.map +1 -0
  72. package/dist/sheet.js +94 -0
  73. package/dist/sheet.js.map +1 -0
  74. package/dist/timings.d.ts +46 -0
  75. package/dist/timings.d.ts.map +1 -0
  76. package/dist/timings.js +108 -0
  77. package/dist/timings.js.map +1 -0
  78. package/dist/types.d.ts +86 -1
  79. package/dist/types.d.ts.map +1 -1
  80. package/dist/types.js +8 -0
  81. package/dist/types.js.map +1 -1
  82. package/package.json +2 -2
  83. package/skill/SKILL.md +34 -10
package/README.md CHANGED
@@ -1,214 +1,84 @@
1
1
  # pre-post
2
2
 
3
- Before/after screenshots for pull requests. One command detects the routes your branch
4
- changed, captures each one before and after, uploads the images to GitHub, and puts them at
5
- the top of the PR description where a reviewer can judge them at a glance.
6
-
7
- It works out both sides itself, cheapest option first:
8
-
9
- | | Pre (the baseline) | Post (this branch) |
10
- |---|---|---|
11
- | 1 | `--before` | `--after` |
12
- | 2 | `before` in `.pre-post.json` | the preview deployment for this commit |
13
- | 3 | the production deployment for the base commit | a local dev server |
14
- | 4 | whatever is on production now | |
15
- | 5 | the base commit, served locally | |
16
-
17
- Rows 2 to 4 need no dev environment at all, which is the point: a preview deployment and a
18
- production URL are enough for anyone who can open the PR. Deployments come from the GitHub
19
- Deployments API, so Vercel, Cloudflare Pages, Netlify and Render all work with no extra
20
- token and nothing to configure; a host that records only a commit status is read from the
21
- deployment bot's own PR comment instead. Row 4 covers repositories that do not deploy every
22
- push to their default branch, so nothing is deployed at the fork point — it prints which
23
- commit Pre actually came from rather than implying the base.
24
-
25
- pre-post never guesses a baseline. If no deployment can be found it says so and tells you
26
- the one flag that fixes it, because a baseline that is quietly the wrong site reads as 100%
27
- changed on every route and looks exactly like a real result.
28
-
29
- The last baseline needs no network at all: it checks the base commit into a throwaway
30
- worktree and boots its dev script. That keeps pre-post working inside a sandboxed agent
31
- container, a CI job, or behind an egress allowlist — and it compares against exactly what
32
- the branch forked from, rendered in the same browser as the Post side.
33
-
34
- > Originally forked from [before-and-after](https://github.com/vercel-labs/before-and-after) by James Clements / Vercel Labs.
3
+ **See the change before you read the diff.**
4
+
5
+ pre-post puts before/after screenshots of every page your branch touched at the top of the
6
+ pull request. One command finds the changed routes, captures Pre and Post, highlights what
7
+ moved, and updates the PR description. No setup, no pasting images.
35
8
 
36
9
  ```
37
10
  $ npx -y @juangadm/pre-post@latest pr
38
11
 
39
12
  Routes (nextjs-app, 41ms): /, /pricing
40
- / medium src/app/page.tsx imports src/components/ui/button.tsx (2 hops)
41
- /pricing medium src/app/pricing/page.tsx imports src/components/ui/button.tsx (2 hops)
42
- Capturing 8 screenshots (2 route(s) × 2 viewport(s)) ...
43
- changed /pricing @ mobile (1.42%, 1811ms)
13
+ Capturing 4 screenshots (2 route(s) × 1 viewport(s)) ...
44
14
  changed /pricing @ desktop (0.64%, 2036ms)
45
- same / @ mobile (0.00%, 1990ms)
46
15
  same / @ desktop (0.00%, 2211ms)
47
- Publishing 10 image(s) to acme/web@pre-post-assets ...
48
16
  Updated PR description: https://github.com/acme/web/pull/42
49
- pre-post · PR #42 · 2 route(s) · 2 viewport(s) · 6.8s
50
- / desktop no change
51
- / mobile no change
52
- /pricing desktop 0.64% changed
53
- /pricing mobile 1.4% changed
54
- Comment: https://github.com/acme/web/pull/42
55
17
  ```
56
18
 
57
- ## How it works
58
-
59
- 1. **Routes.** Diffs the branch against the merge base with `main` — fetching that branch
60
- first when the checkout does not have it, which is the normal shape in CI and in the
61
- web/sandbox editors. When no shared history can be established it stops with one
62
- sentence rather than reporting an empty diff, and `--base <ref>` names the commit
63
- directly. Then it follows the import
64
- graph: a change to `components/ui/button.tsx` marks every page that imports it. Next.js
65
- App Router and Pages Router, Vite apps (React Router, file-based `src/pages`), and a
66
- generic fallback. Monorepos are handled by picking the app that owns the changed files.
67
- 2. **Capture.** Playwright + Chromium headless shell. The page's own clock is held
68
- still while it loads and then run forward by a fixed budget, so a page that animates
69
- on a timer is photographed at the same frame on both sides however fast each host
70
- answered. Reduced motion, animations finished, caret hidden, fonts and images settled,
71
- layout stable, lazy content primed. 2x device scale, full page (capped at 2400 CSS
72
- px), desktop (add mobile with `-r`).
73
- All routes and viewports run concurrently.
74
- 3. **Diff.** Pure-JS pixel comparison in worker threads. Reports the percentage changed,
75
- the bounding box, and a tight crop of the changed region. A route counts as changed when
76
- the painted difference covers at least `minChangedArea` CSS pixels² (default 100, roughly
77
- a third of a 16px icon) or at least `threshold` of the canvas — the first rule is what
78
- decides on a page, the second on a small image.
79
- 4. **Layout shift.** A padding change near the top of a page moves everything below it, and
80
- pixel diffing counts every moved pixel as changed: a change a designer would call
81
- "slightly roomier" reads as most of the page repainted, and the crop is suppressed just
82
- when it would be most useful. So the two sides are checked for a single vertical offset
83
- first — how far the content moved, and from which row. When one is found, Pre is re-spaced
84
- into Post's layout and the two are compared there: the crop comes from that pair, and the
85
- comment says `Content shifted down 48px` instead of quoting a percentage. Rows Post gained
86
- are left as background rather than skipped, so content inserted above the shift — the
87
- banner that caused it — still reads as new rather than as nothing. The offset is only
88
- accepted when it accounts for most of the difference across the rows both sides share.
89
- The raw numbers are left alone: a move is a visual change, and reporting less of one would
90
- hide it. Reflow, where content moves both across and down, has no single offset to find,
91
- so it is reported the way it always was.
92
- 5. **Publish.** Images go to a `pre-post-assets` branch in the same repository via the GitHub
93
- API, as one commit per run. Nothing is committed to the PR branch, no CI is triggered,
94
- and the blob URLs render on private repos — a screenshot is visible to exactly whoever
95
- can see the repository. `pre-post prune` removes images for PRs closed more than 90 days
96
- ago, but note that it commits a deletion rather than rewriting history: the older commits
97
- still hold the blobs, so a link handed out earlier keeps working. Treat anything captured
98
- as permanent, and think twice before pointing pre-post at a preview holding real data.
99
- 6. **Describe.** The images go in a delimited block at the top of the PR description,
100
- replaced in place on every run and leaving your own text untouched. Changed routes show
101
- a Pre/Post crop with the full page collapsed underneath; unchanged routes fold into a
102
- single line. If the PR cannot be edited — a fork, a read-only token — it falls back to
103
- one sticky comment.
104
-
105
- ## Install
106
-
107
- Nothing to install. `npx -y @juangadm/pre-post@latest pr` downloads the CLI and, on first
108
- use, the Chromium headless shell (~80 MB). You need Node 20+ and a GitHub token: either
109
- `gh auth login` or `GH_TOKEN`.
110
-
111
- As a Claude Code skill:
19
+ ## Get started
20
+
21
+ Pick one. None of them installs anything permanently.
22
+
23
+ **In Claude Code**: install the skill once, then say `/pre-post` after a UI change.
112
24
 
113
25
  ```bash
114
26
  npx skills add juangadm/pre-post -y
115
27
  ```
116
28
 
117
- Then say `/pre-post` after making UI changes.
118
-
119
- ## Usage
29
+ **In a terminal**: run it on a branch with an open PR.
120
30
 
121
31
  ```bash
122
- pre-post pr # everything, on the current branch's PR
123
- pre-post pr --before https://acme.com # pin the baseline (saved to .pre-post.json)
124
- pre-post pr --no-local-baseline # never build the base commit locally
125
- pre-post pr --routes /pricing,/docs # explicit routes
126
- pre-post pr --viewports desktop,1440x900 # custom viewports
127
- pre-post pr --dry-run # capture + diff locally, post nothing
128
- pre-post pr --json # machine-readable output
129
-
130
- pre-post https://acme.com http://localhost:3000 --routes /pricing # ad-hoc comparison
131
- pre-post before.png after.png # diff two images
132
- pre-post detect # which routes does this branch touch?
133
- pre-post login https://staging.acme.com # sign in once; the session is reused
134
- pre-post prune --days 90 # clean up the assets branch
135
- pre-post doctor # browser, token, dev server, config
32
+ npx -y @juangadm/pre-post@latest pr
136
33
  ```
137
34
 
138
- When something needs a human, the CLI exits with code 3 and one sentence saying what to do
139
- (log in, start the dev server, pass `--before`). Re-running picks up where it left off.
35
+ **On every PR, automatically**: add the [GitHub Action](https://github.com/juangadm/pre-post/blob/main/docs/github-action.md). No tokens or
36
+ secrets needed.
140
37
 
141
- Exit codes, so a script or an agent can branch on them:
38
+ ### You'll need
142
39
 
143
- | code | meaning |
144
- |---|---|
145
- | 0 | done; for `doctor`, `pre-post pr` can run |
146
- | 1 | the run failed — every capture errored, or an unexpected error; for `doctor`, a required check failed |
147
- | 2 | the arguments could not be parsed |
148
- | 3 | something needs a human; the message says what |
149
-
150
- `doctor` marks a check **FAIL** only when `pre-post pr` has no way to proceed without it —
151
- the browser, a GitHub token, and being inside a git repository. Everything else prints as
152
- **note**: no dev server running, or no `--before` saved, narrows *which* strategy a run
153
- picks rather than stopping it, so those never change the exit code.
154
-
155
- Results go into a delimited block at the top of the PR description, which re-runs replace in
156
- place, leaving your own text untouched. If the PR cannot be edited — a fork, a read-only
157
- token — it falls back to a single sticky comment.
158
-
159
- ## Configuration
160
-
161
- Optional `.pre-post.json` in the repo root:
162
-
163
- ```json
164
- {
165
- "before": "https://acme.com",
166
- "routes": ["/"],
167
- "samples": { "/blog/[slug]": "/blog/hello-world" },
168
- "viewports": ["desktop"],
169
- "fullPage": true,
170
- "maxHeight": 2400,
171
- "scale": 2,
172
- "threshold": 0.001,
173
- "minChangedArea": 100,
174
- "maxRoutes": 6,
175
- "ignore": ["apps/docs"],
176
- "headers": {},
177
- "assetsBranch": "pre-post-assets"
178
- }
179
- ```
40
+ - **Node 20+**
41
+ - **A GitHub token** with write access: `gh auth login`, or set `GH_TOKEN`
42
+ - **An open PR**: without one, pre-post prints the markdown for you to paste
180
43
 
181
- Environment:
182
-
183
- | Variable | Purpose |
184
- |---|---|
185
- | `GH_TOKEN` / `GITHUB_TOKEN` | GitHub token (default: `gh auth token`) |
186
- | `VERCEL_AUTOMATION_BYPASS_SECRET` | Bypass Vercel Deployment Protection on preview and production URLs |
187
- | `PRE_POST_CONCURRENCY` | Parallel pages (default 6) |
188
- | `PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH` | Use a specific Chromium binary |
189
- | `GH_REPO` | `owner/repo` when the remote URL cannot be parsed |
44
+ The first run downloads a small browser (~80 MB). Stuck? Run
45
+ `npx -y @juangadm/pre-post@latest doctor`.
190
46
 
191
- ## Library
47
+ ## Everyday commands
192
48
 
193
- ```ts
194
- import { runPr, captureScreenshot, diffImages, detectRoutesForRepo } from '@juangadm/pre-post';
49
+ ```bash
50
+ pre-post pr # screenshot this branch's PR
51
+ pre-post pr --routes /pricing,/docs # only these pages
52
+ pre-post pr -r # desktop and mobile
53
+ pre-post pr --before https://acme.com # set the "before" site (remembered)
54
+ pre-post pr --dry-run # try it without posting
55
+ pre-post login https://staging.acme.com # sign in once for protected sites
56
+ pre-post doctor # check your setup
195
57
  ```
196
58
 
197
- ## Development
59
+ If pre-post needs something from you, it stops with one sentence saying exactly what.
198
60
 
199
- ```bash
200
- pnpm install
201
- pnpm build
202
- pnpm test:unit
203
- TEST_BROWSER=true pnpm test # needs a Chromium; npx playwright-core install chromium-headless-shell
204
- ```
61
+ ## How it decides what "before" is
62
+
63
+ Post (your branch) is the PR's preview deployment, or a local dev server. Pre is the
64
+ production site your branch forked from. When nothing is deployed, pre-post builds the base
65
+ commit itself, so it works in CI and in sandboxes too. It never guesses: if it can't find a
66
+ trustworthy baseline, it tells you which flag to pass.
205
67
 
206
- pnpm and npm cannot share a `node_modules`. pnpm builds a symlinked tree that npm
207
- cannot read, so `npm install` over it fails with `Cannot read properties of null
208
- (reading 'edgesOut')`, which names none of that. Run `rm -rf node_modules
209
- site/node_modules` before switching either way. npm also installs the CLI only —
210
- `site/` is a pnpm workspace member, so use pnpm to work on the site.
68
+ Screenshots are stored on a `pre-post-assets` branch in your own repo, so only people who can
69
+ see the repo can see them. Nothing is committed to your PR branch.
70
+
71
+ ## Learn more
72
+
73
+ | | |
74
+ |---|---|
75
+ | [How it works](https://github.com/juangadm/pre-post/blob/main/docs/how-it-works.md) | How Pre and Post are chosen, capture, diffing, layout shifts |
76
+ | [GitHub Action](https://github.com/juangadm/pre-post/blob/main/docs/github-action.md) | Run on every PR with no per-person setup |
77
+ | [Reference](https://github.com/juangadm/pre-post/blob/main/docs/reference.md) | All commands, exit codes, `.pre-post.json`, environment variables |
78
+ | [Screenshot storage](https://github.com/juangadm/pre-post/blob/main/docs/storage.md) | How much space it uses and how to prune it |
79
+ | [Contributing](https://github.com/juangadm/pre-post/blob/main/CONTRIBUTING.md) | Build and test locally |
211
80
 
212
- ## License
81
+ ## Credits
213
82
 
214
- MIT
83
+ Forked from [before-and-after](https://github.com/vercel-labs/before-and-after) by James
84
+ Clements / Vercel Labs. MIT licensed.
@@ -11,12 +11,24 @@
11
11
  * The cost is time: a checkout, an install and a dev-server boot. So callers
12
12
  * reach for it after a reachable deployment, not before one.
13
13
  */
14
+ import { ChildProcess } from 'child_process';
14
15
  import { NeedsHumanError } from './errors.js';
16
+ import { Stopwatch } from './timings.js';
15
17
  export interface LocalBaseline {
16
18
  url: string;
17
19
  /** Stop the dev server and delete the worktree. Safe to call twice. */
18
20
  stop: () => Promise<void>;
21
+ /** Everything the dev server printed, so a failure can be explained with its own words. */
22
+ logFile?: string;
19
23
  }
24
+ /**
25
+ * Where a dev server's output goes: a file per server, never the terminal.
26
+ *
27
+ * It used to go nowhere (`stdio: 'ignore'`), so when a server answered 500
28
+ * there was no way to say why. A file rather than a pipe: the server is
29
+ * detached and must not stall when this process stops reading.
30
+ */
31
+ export declare function serverLogFile(side: 'pre' | 'post', port: number): string;
20
32
  interface PackageManager {
21
33
  bin: string;
22
34
  install: string[];
@@ -105,6 +117,27 @@ export declare function servableDir(treeRoot: string, appPrefix?: string): {
105
117
  * are exactly the ones that hold secrets.
106
118
  */
107
119
  export declare function copyEnvFiles(from: string, to: string, appPrefix?: string): string[];
120
+ /** Does this variable name the app's own origin? */
121
+ export declare function isOriginKey(key: string): boolean;
122
+ /** One env file's text with every origin variable pointed at `origin`. */
123
+ export declare function rewriteEnvOrigins(text: string, origin: string): {
124
+ text: string;
125
+ keys: string[];
126
+ };
127
+ /**
128
+ * Point the worktree's copied env files at the baseline's real origin.
129
+ *
130
+ * The copies name the port the developer's own dev server uses, and the
131
+ * baseline is on an ephemeral one. An app whose auth library is told it lives
132
+ * at :3000 while it is answering on :51234 rejects its own callbacks: the page
133
+ * renders, the session never resolves, and every capture is a loading
134
+ * skeleton. That reads as success — a screenshot came back — which is the
135
+ * failure mode this whole file exists to avoid.
136
+ *
137
+ * Same constraint as the copy: only ever writes inside `dir`, and returns
138
+ * names, never values.
139
+ */
140
+ export declare function pointEnvFilesAt(dir: string, origin: string, appPrefix?: string): string[];
108
141
  /** An OS-assigned free port. */
109
142
  export declare function freePort(): Promise<number>;
110
143
  /**
@@ -127,7 +160,7 @@ export interface InstallResult {
127
160
  /** In order. Length 2 when a first failure earned a retry. */
128
161
  attempts: InstallAttempt[];
129
162
  }
130
- export type InstallRunner = (bin: string, argv: string[], cwd: string, timeoutMs: number) => InstallAttempt;
163
+ export type InstallRunner = (bin: string, argv: string[], cwd: string, timeoutMs: number, signal?: AbortSignal) => Promise<InstallAttempt>;
131
164
  /**
132
165
  * Does this look like npm refusing to resolve a peer range?
133
166
  *
@@ -146,11 +179,11 @@ export declare function isPeerConflict(output: string): boolean;
146
179
  * the comparison this tool exists to make is between two renders of the same
147
180
  * app, not between two dependency trees.
148
181
  */
149
- export declare function installDeps(pm: PackageManager, cwd: string, timeoutMs: number, run?: InstallRunner): InstallResult;
182
+ export declare function installDeps(pm: PackageManager, cwd: string, timeoutMs: number, run?: InstallRunner, signal?: AbortSignal): Promise<InstallResult>;
150
183
  /**
151
184
  * The install failed and nothing else can rescue this run.
152
185
  *
153
- * Its own error rather than a quiet null, for the reason docs/portability.md
186
+ * Its own error rather than a quiet null, for the reason docs/internal/portability.md
154
187
  * §1 gives about base resolution: a run that compared nothing must not exit
155
188
  * clean. A null here fell through to "no baseline", and on a repository with a
156
189
  * configured production URL it fell through to comparing against that instead
@@ -175,17 +208,103 @@ export declare class BaselineInstallError extends NeedsHumanError {
175
208
  readonly result: InstallResult;
176
209
  constructor(result: InstallResult, where: string, ranIn: string);
177
210
  }
211
+ /** A command to run between the install and the dev server. */
212
+ export interface SetupStep {
213
+ /** The command as a reader would type it, for the log. */
214
+ label: string;
215
+ bin: string;
216
+ argv: string[];
217
+ cwd: string;
218
+ /** Run through a shell, for a command a human wrote as one string. */
219
+ shell?: boolean;
220
+ }
221
+ /**
222
+ * Build the workspace packages the app imports, before the dev server starts.
223
+ *
224
+ * A monorepo app depends on sibling packages that ship compiled output, and a
225
+ * fresh install does not produce it — `turbo dev` normally would, but the dev
226
+ * script this file runs is the app's own. So the app boots against a package
227
+ * with no `dist`, and the baseline is an error page or a shell.
228
+ *
229
+ * `^...` is turbo's "dependencies of, excluding itself": the siblings get
230
+ * built, the app is left to the dev server.
231
+ */
232
+ export declare function turboDependencyBuild(treeRoot: string, appDir: string): SetupStep | null;
233
+ /**
234
+ * What to run between install and dev: `baselineSetup` if the repository
235
+ * configured one, else turbo's dependency build when this is a turborepo.
236
+ *
237
+ * The configured command wins outright. It is the general answer — one line in
238
+ * `.pre-post.json` for a repository whose baseline needs a codegen step, a
239
+ * prisma generate, a build this tool has never heard of — and a repository
240
+ * that names one has already told us the guess is not wanted.
241
+ */
242
+ export declare function setupStep(treeRoot: string, appDir: string, configured?: string): SetupStep | null;
243
+ /**
244
+ * The configured setup command failed, and nothing else can rescue this run.
245
+ *
246
+ * One sentence, and the output goes to the log above it, for the reason
247
+ * `installFailureHint` gives. The directory named is the caller's own, never
248
+ * the worktree: cleanup has deleted the worktree by the time anyone reads it.
249
+ */
250
+ export declare function setupFailureHint(label: string, ranIn: string): string;
251
+ export declare class BaselineSetupError extends NeedsHumanError {
252
+ constructor(label: string, ranIn: string);
253
+ }
254
+ /**
255
+ * Can the base commit use the dependencies this checkout already installed?
256
+ *
257
+ * Only when no install input differs between the base and the working tree,
258
+ * uncommitted edits included, and a lockfile is tracked (without one there is
259
+ * nothing to prove the trees match). Returns the package directories whose
260
+ * node_modules to bring across, or null to install as usual. Once the diff is
261
+ * clean, the index lists the same package.json files the base has, so it
262
+ * answers that without walking the base commit's whole tree.
263
+ */
264
+ export declare function reusableInstall(repoRoot: string, sha: string): Promise<string[] | null>;
265
+ /**
266
+ * Bring this checkout's installed dependencies into the base worktree.
267
+ * True when every one was copied; on any failure the partial copies are
268
+ * removed so the normal install starts from a clean tree.
269
+ */
270
+ export declare function reuseInstall(repoRoot: string, worktree: string, dirs: string[]): Promise<boolean>;
271
+ /** How long a dev server gets to exit on SIGTERM before it is killed. */
272
+ export declare const SERVER_STOP_TIMEOUT_MS = 5000;
273
+ /**
274
+ * SIGTERM the server's process group, wait for it to exit, then make sure.
275
+ *
276
+ * The wait is on the child itself — the package manager, which exits once the
277
+ * server under it has — because Node reaps it, so its exit is a fact. Asking
278
+ * the group with signal 0 was not: in a container whose PID 1 does not reap,
279
+ * dead members linger as zombies that still answer, and every stop waited out
280
+ * the full timeout. Once the child is gone, or `timeoutMs` has passed, the
281
+ * whole group is SIGKILLed, so nothing it started is left writing into a
282
+ * worktree about to be removed.
283
+ */
284
+ export declare function stopGroup(child: ChildProcess, timeoutMs?: number): Promise<void>;
178
285
  export interface BaselineOptions {
179
286
  repoRoot: string;
180
287
  /** Commit to serve. Omit to serve the working tree as it stands. */
181
288
  sha?: string;
182
289
  /** Directory holding the app's package.json, relative to the repo root. */
183
290
  appPrefix?: string;
291
+ /**
292
+ * Command to run between the install and the dev server, from
293
+ * `.pre-post.json`. Omit to let a turborepo's dependency build be inferred.
294
+ */
295
+ setup?: string;
184
296
  /** Budget for install + boot. */
185
297
  timeoutMs?: number;
186
298
  /** Injectable for tests; defaults to a real PATH scan. */
187
299
  pathHas?: (bin: string) => boolean;
188
300
  log?: (msg: string) => void;
301
+ /** Records how long each step took, under `pre.*` or `post.*`. */
302
+ timings?: Stopwatch;
303
+ /**
304
+ * Abort the boot: the caller no longer needs this side. Whatever it started
305
+ * is stopped and cleaned up, and it resolves null.
306
+ */
307
+ signal?: AbortSignal;
189
308
  }
190
309
  /**
191
310
  * Check out `sha` into a throwaway worktree, install, and start its dev server.
@@ -1 +1 @@
1
- {"version":3,"file":"baseline.d.ts","sourceRoot":"","sources":["../src/baseline.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"baseline.d.ts","sourceRoot":"","sources":["../src/baseline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAS,YAAY,EAA0B,MAAM,eAAe,CAAC;AAK5E,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAE9C,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAGzC,MAAM,WAAW,aAAa;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,uEAAuE;IACvE,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1B,2FAA2F;IAC3F,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,KAAK,GAAG,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAIxE;AAGD,UAAU,cAAc;IACtB,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,kFAAkF;IAClF,GAAG,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,MAAM,EAAE,CAAC;IAClD;;;;;;;;;;;;;;;OAeG;IACH,KAAK,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,EAAE,CAAC;QAAC,IAAI,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAA;KAAE,CAAC;CAC/D;AAgBD;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,cAAc,CAUnF;AAED;;;;;;GAMG;AACH,wBAAgB,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,OAAO,CAUjF;AAED,MAAM,WAAW,aAAa;IAC5B,6EAA6E;IAC7E,EAAE,EAAE,cAAc,GAAG,IAAI,CAAC;IAC1B,yEAAyE;IACzE,QAAQ,EAAE,cAAc,CAAC;CAC1B;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,qBAAqB,CACnC,GAAG,EAAE,MAAM,EACX,QAAQ,CAAC,EAAE,MAAM,EACjB,GAAG,GAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAgB,GACrC,aAAa,CAIf;AAED;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAUxG;AAUD;;;;;;;;;;GAUG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAkBnF;AA+BD,oDAAoD;AACpD,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAGhD;AAED,0EAA0E;AAC1E,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,EAAE,CAAA;CAAE,CAYhG;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAkBzF;AAED,gCAAgC;AAChC,wBAAgB,QAAQ,IAAI,OAAO,CAAC,MAAM,CAAC,CAS1C;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,EAAE,EAAE,OAAO,CAAC;IACZ,mDAAmD;IACnD,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,OAAO,CAAC;IACZ,8DAA8D;IAC9D,QAAQ,EAAE,cAAc,EAAE,CAAC;CAC5B;AAED,MAAM,MAAM,aAAa,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,cAAc,CAAC,CAAC;AAwC3I;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAEtD;AAED;;;;;;;;;GASG;AACH,wBAAsB,WAAW,CAC/B,EAAE,EAAE,cAAc,EAClB,GAAG,EAAE,MAAM,EACX,SAAS,EAAE,MAAM,EACjB,GAAG,GAAE,aAA0B,EAC/B,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,aAAa,CAAC,CAQxB;AAED;;;;;;;;;GASG;AACH;;;;;;;;;;;;GAYG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAEvE;AAED,qBAAa,oBAAqB,SAAQ,eAAe;aAC3B,MAAM,EAAE,aAAa;gBAArB,MAAM,EAAE,aAAa,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM;CAIhF;AAED,+DAA+D;AAC/D,MAAM,WAAW,SAAS;IACxB,0DAA0D;IAC1D,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,sEAAsE;IACtE,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAcD;;;;;;;;;;GAUG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAWvF;AAED;;;;;;;;GAQG;AACH,wBAAgB,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAIjG;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAErE;AAED,qBAAa,kBAAmB,SAAQ,eAAe;gBACzC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM;CAIzC;AAwBD;;;;;;;;;GASG;AACH,wBAAsB,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,GAAG,IAAI,CAAC,CAoB7F;AAqDD;;;;GAIG;AACH,wBAAsB,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAavG;AAaD,yEAAyE;AACzE,eAAO,MAAM,sBAAsB,OAAQ,CAAC;AAE5C;;;;;;;;;;GAUG;AACH,wBAAsB,SAAS,CAAC,KAAK,EAAE,YAAY,EAAE,SAAS,SAAyB,GAAG,OAAO,CAAC,IAAI,CAAC,CAWtG;AAgBD,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,oEAAoE;IACpE,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,2EAA2E;IAC3E,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,iCAAiC;IACjC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,0DAA0D;IAC1D,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC;IACnC,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5B,kEAAkE;IAClE,OAAO,CAAC,EAAE,SAAS,CAAC;IACpB;;;OAGG;IACH,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED;;;;;;GAMG;AACH,wBAAsB,eAAe,CAAC,IAAI,EAAE,eAAe,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC,CAE1F;AAED;;;;;;;GAOG;AACH,wBAAsB,gBAAgB,CAAC,IAAI,EAAE,IAAI,CAAC,eAAe,EAAE,KAAK,CAAC,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC,CAExG"}